/*
 * Copyright (c) 2024-2024 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
#include "svc_softap_sess.h"
#include "iotc_log.h"
#include "utils_assert.h"
#include "comm_def.h"
#include "trans_buffer_inner.h"
#include "coap_endpoint.h"
#include "coap_codec_udp.h"
#include "coap_endpoint_server.h"
#include "coap_endpoint_retrans.h"
#include "coap_endpoint_event_source.h"
#include "utils_common.h"
#include "wifi_sched_fd_watch.h"
#include "securec.h"
#include "iotc_network.h"
#include "svc_softap_coap.h"
#include "svc_softap_retrans.h"
#include "sched_event_loop.h"
#include "sched_timer.h"
#include "iotc_conf.h"
#include "security_random.h"
#include "iotc_wifi.h"
#include "service_proxy.h"
#include "iotc_svc.h"
#include "iotc_svc_dev.h"
#include "iotc_errcode.h"
#include "product_adapter.h"

/* 开始speke协商后需要在30s内协商完成 */
#define PEER_WAIT_SPEKE_NEGO_TIMEOUT UTILS_SEC_TO_MS(30)
/* 每5秒检查一次sta列表，检查当前对端是否已断开softap */
#define SOFTAP_STA_CHECK_TIMER_PERIOD UTILS_SEC_TO_MS(5)

static const char *SOFTAP_NAME = "SOFTAP";

static SoftapPeerSess *FindSoftapPeerSession(SoftapSess *sess, const SocketAddr *addr)
{
    SoftapPeerSess *empty = NULL;
    for (uint32_t i = 0; i < IOTC_CONF_SOFTAP_MAX_PEER_SESS_NUM; ++i) {
        if (addr->addr == sess->peerSess[i].addrInfo.addr) {
            return &sess->peerSess[i];
        }
        if (!UTILS_IS_BIT_SET(sess->peerSess[i].bitMap, SOFTAP_PEER_SESS_BIT_MAP_LINK_ACTIVE)) {
            empty = &sess->peerSess[i];
        }
    }
    return empty;
}

static int32_t SoftapGetPinCode(SpekeSession *sess, void *user, uint8_t *pinCode, uint32_t *len)
{
    NOT_USED(user);
    CHECK_RETURN(len != NULL && *len >= IOTC_PINCODE_LEN, IOTC_ERR_PARAM_INVALID);

    *len = IOTC_PINCODE_LEN;
    int32_t ret = ProductProfGetPincode(pinCode, *len);
    if (ret != IOTC_OK) {
        IOTC_LOGW("get pin err %d", ret);
        return ret;
    }
    return IOTC_OK;
}

static void DestroyPeerSession(SoftapPeerSess *peerSess)
{
    if (UTILS_IS_BIT_SET(peerSess->bitMap, SOFTAP_PEER_SESS_BIT_MAP_LINK_ACTIVE)) {
        (void)IotcSoftapDisassociateSta(peerSess->mac, sizeof(peerSess->mac));
    }
    if (peerSess->timer >= 0) {
        SchedTimerRemove(peerSess->timer);
    }
    if (peerSess->speke != NULL) {
        SpekeFreeSession(peerSess->speke);
    }
    (void)memset_s(peerSess, sizeof(SoftapPeerSess), 0, sizeof(SoftapPeerSess));
    peerSess->timer = EVENT_SOURCE_INVALID_TIMER_FD;
}

static void PeerSessTimeoutTimerCallback(int32_t id, void *userData)
{
    CHECK_V_RETURN_LOGW(userData != NULL, "param invalid");
    /* userdata pointer only use to match peer */
    SoftapPeerSess *peerSess = (SoftapPeerSess *)userData;
    SoftapServiceContext *ctx = GetSoftapServiceContext();
    if (ctx == NULL) {
        SchedTimerRemove(id);
        return;
    }

    for (uint32_t i = 0; i < IOTC_CONF_SOFTAP_MAX_PEER_SESS_NUM; ++i) {
        if (&ctx->sess.peerSess[i] != peerSess) {
            continue;
        }
        DestroyPeerSession(&ctx->sess.peerSess[i]);
    }
    return;
}

static int32_t SoftapNotifySpekeFinished(SpekeSession *sess, void *user, int32_t errorCode)
{
    NOT_USED(user);
    SoftapServiceContext *ctx = GetSoftapServiceContext();
    if (ctx == NULL) {
        IOTC_LOGW("softap ctx is null");
        return IOTC_CORE_WIFI_NETCFG_ERR_SOFTAP_INVALID_CTX;
    }

    SoftapPeerSess *peerSess = NULL;
    for (uint32_t i = 0; i < IOTC_CONF_SOFTAP_MAX_PEER_SESS_NUM; ++i) {
        if (sess == ctx->sess.peerSess[i].speke) {
            peerSess = &ctx->sess.peerSess[i];
        }
    }
    if (peerSess == NULL) {
        IOTC_LOGW("invalid speke sess");
        return IOTC_CORE_WIFI_NETCFG_ERR_SOFTAP_INVALID_CTX;
    }

    if (errorCode == IOTC_OK) {
        IOTC_LOGN("speke finish ok");
        UTILS_BIT_SET(peerSess->bitMap, SOFTAP_PEER_SESS_BIT_MAP_SPEKE_SESS_CREATED);
        UTILS_BIT_RESET(peerSess->bitMap, SOFTAP_PEER_SESS_BIT_MAP_E2E_SEQ_SET);
        if (peerSess->timer >= 0) {
            SchedTimerRemove(peerSess->timer);
            peerSess->timer = EVENT_SOURCE_INVALID_TIMER_FD;
        }
        return IOTC_OK;
    }

    IOTC_LOGW("speke finish err %d", errorCode);
    DestroyPeerSession(peerSess);
    return IOTC_OK;
}

static int32_t CreatePeerSession(SoftapPeerSess *peerSess, const SocketAddr *addrInfo)
{
    DestroyPeerSession(peerSess);
    IotcStationList *staList = NULL;
    int32_t ret = IotcGetSoftapStationInfo(&staList);
    if (ret != IOTC_OK || staList == NULL || staList->num == 0) {
        IOTC_LOGW("get station list error %d", ret);
        return IOTC_CORE_WIFI_NETCFG_ERR_SOFTAP_INVALID_STATION;
    }

    ret = IOTC_CORE_WIFI_NETCFG_ERR_SOFTAP_INVALID_STATION;
    for (uint32_t i = 0; i < staList->num; ++i) {
        IotcStationInfo *staInfo = &staList->stationList[i];
        if (staInfo->ip != addrInfo->addr) {
            continue;
        }
        ret = memcpy_s(peerSess->mac, sizeof(peerSess->mac), staInfo->mac, sizeof(staInfo->mac));
        if (ret != EOK) {
            ret = IOTC_ERR_SECUREC_MEMCPY;
        }
        break;
    }
    IotcFreeSoftapStationInfo(staList);
    staList = NULL;
    if (ret != IOTC_OK) {
        IOTC_LOGW("get sta mac error %d", ret);
        return ret;
    }

    /* start timer to clean sess if timeout */
    peerSess->timer = SchedTimerAdd(EVENT_SOURCE_TIMER_TYPE_ONCE, PeerSessTimeoutTimerCallback,
        PEER_WAIT_SPEKE_NEGO_TIMEOUT, peerSess);
    if (peerSess->timer < 0) {
        IOTC_LOGW("start speke timer error %d", peerSess->timer);
        return peerSess->timer;
    }

    UTILS_BIT_SET(peerSess->bitMap, SOFTAP_PEER_SESS_BIT_MAP_LINK_ACTIVE);
    peerSess->addrInfo = *addrInfo;
    peerSess->sendSeq = SecurityRandomUint32() % UINT16_MAX;

    /* create speke sess when recv speke packet */
    return IOTC_OK;
}

int32_t SoftapPeerSessInitSpeke(SoftapPeerSess *peerSess)
{
    CHECK_RETURN_LOGW(peerSess != NULL, IOTC_ERR_PARAM_INVALID, "param invalid");
    if (peerSess->speke != NULL) {
        return IOTC_OK;
    }

    SpekeCallback cb = {SoftapGetPinCode, SoftapNotifySpekeFinished};
    peerSess->speke = SpekeInitSession(SPEKE_TYPE_SERVER, &cb, NULL);
    if (peerSess->speke == NULL) {
        IOTC_LOGW("speke init error");
        return IOTC_CORE_COMM_SEC_ERR_SPEKE_CREATE;
    }
    return IOTC_OK;
}

SoftapPeerSess *SoftapGetPeerSess(const SocketAddr *addrInfo, SoftapSess *sess)
{
    CHECK_RETURN_LOGE(addrInfo != NULL && sess != NULL, NULL, "param invalid");
    SoftapPeerSess *peerSess = FindSoftapPeerSession(sess, addrInfo);
    if (peerSess == NULL) {
        IOTC_LOGW("no invalid sess");
        return NULL;
    }
    if (!UTILS_IS_BIT_SET(peerSess->bitMap, SOFTAP_PEER_SESS_BIT_MAP_LINK_ACTIVE)) {
        return NULL;
    }
    return peerSess;
}

SoftapPeerSess *SoftapGetPeerSessCreateIfNotExist(const SocketAddr *addrInfo, SoftapSess *sess)
{
    CHECK_RETURN_LOGE(addrInfo != NULL && sess != NULL, NULL, "param invalid");
    SoftapPeerSess *peerSess = FindSoftapPeerSession(sess, addrInfo);
    if (peerSess == NULL) {
        IOTC_LOGW("no invalid sess");
        return NULL;
    }

    if (!UTILS_IS_BIT_SET(peerSess->bitMap, SOFTAP_PEER_SESS_BIT_MAP_LINK_ACTIVE)) {
        int32_t ret = CreatePeerSession(peerSess, addrInfo);
        if (ret != IOTC_OK) {
            IOTC_LOGW("create peer sess error %d", ret);
            return NULL;
        }
    }
    return peerSess;
}

static int32_t SoftapCoapStackCreate(SoftapSess *sess)
{
    char local[IOTC_IP_STR_MAX_LEN + 1] = {0};
    int32_t ret = IotcGetSoftApIp(local, IOTC_IP_STR_MAX_LEN);
    if (ret != IOTC_OK) {
        IOTC_LOGW("get softap ip error %d", ret);
        return ret;
    }

    SocketUdpInitParam udp = {
        .port = WIFI_SOFTAP_UDP_PORT,
        .localAddr = local,
        .multicastAddr = NULL,
        .broadcastAddr = NULL,
    };

    TransSocket *socket = TransSocketUdpNew(&udp);
    if (socket == NULL) {
        IOTC_LOGW("create socket error");
        return IOTC_CORE_WIFI_TRANS_ERR_SOCKET_UDP_CREATE;
    }

    CoapNetStackParam stackParam = {
        .name = SOFTAP_NAME,
        .socket = socket,
        .sessMsgSize = sizeof(CoapPacket),
        .sessUserData = sess,
        .encoder = CoapUdpEncode,
        .decoder = CoapUdpDecode,
        .endpointUserData = sess,
    };
    ret = CoapNetStackCreate(&sess->coapStack, &stackParam);
    if (ret != IOTC_OK) {
        IOTC_LOGW("stack create error %d", ret);
        TransSocketFree(socket);
        return ret;
    }

    return IOTC_OK;
}

static int32_t SoftapSessionSetup(SoftapSess *sess)
{
    TransSessAddRecvTailHandler(sess->coapStack.sess, SoftapCoapMsgRecvPreProcess, "pre", NULL);
    TransSessAddRecvTailHandler(sess->coapStack.sess, SoftapCoapMsgRecvBase64DecodeProcess, "base64_decode", NULL);
    TransSessAddRecvTailHandler(sess->coapStack.sess, SoftapCoapMsgRecvDecryptProcess, "decrypt", NULL);

    TransSessAddSendTailHandler(sess->coapStack.sess, SoftapCoapMsgSendEncryptProcess, "encrypt", NULL);
    TransSessAddSendTailHandler(sess->coapStack.sess, SoftapCoapMsgSendBase64EncodeProcess, "base64_encode", NULL);
    TransSessAddSendTailHandler(sess->coapStack.sess, SoftapCoapMsgSendFinalProcess, "final", NULL);

    CoapEndpointSessSetup(sess->coapStack.endpoint);
    return IOTC_OK;
}

static int32_t SoftapCoapEndpointSetup(SoftapSess *sess, const SoftapSvcInitParam *initParam)
{
    /* uri white list for recv not decrypt or not base64 decode */
    static const char *plainUri[] = {STR_URI_SPEKE};
    static const char *noBase64uri[] = {STR_URI_SPEKE};

    sess->plainUri = plainUri;
    sess->plainUriNum = ARRAY_SIZE(plainUri);
    sess->noBase64Uri = noBase64uri;
    sess->noBase64Num = ARRAY_SIZE(noBase64uri);

    static const CoapResource SPEKE_RES[] = {
        {UTILS_BIT(COAP_METHOD_TYPE_POST), STR_URI_SPEKE, NULL, SoftapCoapSpekeReqHandler},
    };
    static const CoapResource CLOUD_SETUP_V2_RES[] = {
        {UTILS_BIT(COAP_METHOD_TYPE_POST), STR_URI_CLOUD_SETUP_V2, NULL, SoftapCoapSetupReqHandler},
    };
    static const CoapResource E2E_CTL_RES[] = {
        {UTILS_BIT(COAP_METHOD_TYPE_POST), STR_E2E_CONTROL, NULL, SoftapCoapE2eCtrlHandler},
    };

    int32_t ret = CoapServerAddResource(sess->coapStack.endpoint, SPEKE_RES, ARRAY_SIZE(SPEKE_RES));
    if (ret != IOTC_OK) {
        IOTC_LOGW("add speke coap res error %d", ret);
        return ret;
    }

    if (UTILS_IS_BIT_SET(initParam->bitMap, IOTC_WIFI_SERVICE_SOFTAP_NETCFG)) {
        ret = CoapServerAddResource(sess->coapStack.endpoint, CLOUD_SETUP_V2_RES, ARRAY_SIZE(CLOUD_SETUP_V2_RES));
        if (ret != IOTC_OK) {
            IOTC_LOGW("add cloud setup coap res error %d", ret);
            return ret;
        }
    }

    if (UTILS_IS_BIT_SET(initParam->bitMap, IOTC_WIFI_SERVICE_SOFTAP_E2E_CTRL)) {
        ret = CoapServerAddResource(sess->coapStack.endpoint, E2E_CTL_RES, ARRAY_SIZE(E2E_CTL_RES));
        if (ret != IOTC_OK) {
            IOTC_LOGW("add e2e ctrl coap res error %d", ret);
            return ret;
        }
    }

    /* use max send buffer for retrans, ensure single message can be retransmitted */
    ret = CoapEndpointRetransEnable(sess->coapStack.endpoint, SoftapCoapRetransCheckFunc,
        TransGetBufferSize(TRANS_BUFFER_SEND_BUFFER_RES_SIZE));
    if (ret != IOTC_OK) {
        IOTC_LOGW("enable coap retrans error %d", ret);
        return ret;
    }

    return IOTC_OK;
}

static void StationCheckTimerCallback(int32_t id, void *userData)
{
    NOT_USED(userData);
    SoftapServiceContext *ctx = GetSoftapServiceContext();
    if (ctx == NULL) {
        SchedTimerRemove(id);
        return;
    }

    bool isStaExist = false;
    for (uint32_t i = 0; i < IOTC_CONF_SOFTAP_MAX_PEER_SESS_NUM; ++i) {
        if (!UTILS_IS_BIT_SET(ctx->sess.peerSess[i].bitMap, SOFTAP_PEER_SESS_BIT_MAP_LINK_ACTIVE)) {
            continue;
        }
        isStaExist = true;
        break;
    }

    if (!isStaExist) {
        return;
    }

    IotcStationList *staList = NULL;
    int32_t ret = IotcGetSoftapStationInfo(&staList);
    if (ret != IOTC_OK) {
        IOTC_LOGW("get station list error %d", ret);
        return;
    }

    for (uint32_t i = 0; i < IOTC_CONF_SOFTAP_MAX_PEER_SESS_NUM; ++i) {
        SoftapPeerSess *peerSess = &ctx->sess.peerSess[i];
        if (!UTILS_IS_BIT_SET(ctx->sess.peerSess[i].bitMap, SOFTAP_PEER_SESS_BIT_MAP_LINK_ACTIVE)) {
            continue;
        }

        bool isPeerExits = false;
        for (uint32_t j = 0; j < staList->num; ++j) {
            IotcStationInfo *staInfo = &staList->stationList[j];
            if (peerSess->addrInfo.addr != staInfo->ip ||
                memcmp(peerSess->mac, staInfo->mac, sizeof(peerSess->mac)) != 0) {
                continue;
            }
            isPeerExits = true;
            break;
        }
        if (!isPeerExits) {
            DestroyPeerSession(peerSess);
        }
    }
    IotcFreeSoftapStationInfo(staList);
    return;
}

static int32_t StartStationCheckTimer(SoftapSess *sess)
{
    sess->staTimer = SchedTimerAdd(EVENT_SOURCE_TIMER_TYPE_REPEAT, StationCheckTimerCallback,
        SOFTAP_STA_CHECK_TIMER_PERIOD, NULL);
    if (sess->staTimer < 0) {
        IOTC_LOGW("start sta check timer error %d", sess->staTimer);
        return sess->staTimer;
    }
    return IOTC_OK;
}

int32_t CreateSoftapSess(SoftapSess *sess, const SoftapSvcInitParam *initParam)
{
    CHECK_RETURN(sess != NULL && initParam != NULL, IOTC_ERR_PARAM_INVALID);

    int32_t ret;
    do {
        ret = SoftapCoapStackCreate(sess);
        if (ret != IOTC_OK) {
            IOTC_LOGW("create coap stack error %d", ret);
            break;
        }

        ret = SoftapSessionSetup(sess);
        if (ret != IOTC_OK) {
            IOTC_LOGW("softap sess setup error %d", ret);
            break;
        }

        ret = SoftapCoapEndpointSetup(sess, initParam);
        if (ret != IOTC_OK) {
            IOTC_LOGW("softap endpoint setup error %d", ret);
            break;
        }

        ret = CoapNetStackStart(&sess->coapStack);
        if (ret != IOTC_OK) {
            IOTC_LOGW("stack start error %d", ret);
            break;
        }

        ret = StartStationCheckTimer(sess);
        if (ret != IOTC_OK) {
            break;
        }
        return IOTC_OK;
    } while (0);
    /* 异常处理 */
    DestroySoftapSess(sess);
    return ret;
}

void DestroySoftapSess(SoftapSess *sess)
{
    CHECK_V_RETURN(sess != NULL);
    if (sess->staTimer >= 0) {
        SchedTimerRemove(sess->staTimer);
    }
    CoapNetStackDestroy(&sess->coapStack);
    for (uint32_t i = 0; i < IOTC_CONF_SOFTAP_MAX_PEER_SESS_NUM; ++i) {
        DestroyPeerSession(&sess->peerSess[i]);
    }
    (void)memset_s(sess, sizeof(SoftapSess), 0, sizeof(SoftapSess));
}